/**
 * Copyright 2016-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.inmorn.extspring.util;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;

public final class ClassUtils {
	private static final Logger LOG = LoggerFactory.getLogger(ClassUtils.class);

	private ClassUtils(){}

	/**
	 * 通过类名反射得到类元.
	 * 使用的类名应该和<code>java.lang.Class#getName()</code>一致
	 *
	 * @see java.lang.Class#getName()
	 * @param className 类名
	 * @return 类元
	 * @throws ClassNotFoundException 类不存在时抛出
	 */
	public static Class<?> forName(String className) throws ClassNotFoundException {
		if (className == null)
			throw new java.lang.NullPointerException("类名不能为空！");
		return Class.forName(className);
	}

	/**
	 * 通过标准类名反射得到类元.
	 * <p/>
	 * 与forName主要区别在于数组名使用："java.lang.Object[]"，在forName时使用："[Ljava.lang.Object;"
	 *
	 * @param className 标准类名
	 * @return 类元
	 * @throws ClassNotFoundException 类不存在时抛出
	 */
	public static Class<?> forCanonicalName(String className) throws ClassNotFoundException {
		if (className == null)
			throw new java.lang.NullPointerException("类名不能为空！");
		if (className.endsWith("[]"))
			className = "[L" + className.substring(0, className.length() - 2) + ";";
		return Class.forName(className);
	}

	/**
	 * 获取对象的属性值
	 *
	 * @param object 对象实例
	 * @param property 属性名
	 * @return 属性的值
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public static Object getObjectProperty(Object object, String property) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		if (object == null)
			return null;
		Method getter = getClassGetter(object.getClass(), property);
		return getter.invoke(object, new Object[0]);
	}

	/**
	 * 获取对象的多个属性值
	 *
	 * @param object 对象实例
	 * @param properties 属性列表
	 * @return 属性集合
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public static Map<String, ?> getObjectProperties(Object object, String[] properties) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		if (object == null || properties == null)
			return null;
		Map<String, Object> map = new HashMap<String, Object>(properties.length);
		for (int i = 0, n = properties.length; i < n; i ++) {
			String prop = properties[i];
			if (prop != null && prop.length() > 0)
				map.put(prop, getObjectProperty(object, prop));
		}
		return map;
	}

	/**
	 * 获取对象的所有属性值
	 *
	 * @param object 对象实例
	 * @return 属性集合
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public static Map<String, Object> getObjectAllProperties(Object object) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		Map<String, Object> map = new HashMap<String, Object>();
		if (object != null) {
			Method[] methods = object.getClass().getMethods();
			for (int i = 0, n = methods.length; i < n; i ++) {
				Method method = methods[i];
				if ((method.getModifiers() & Modifier.PUBLIC) == 1
						&& method.getDeclaringClass() != Object.class
						&& (method.getParameterTypes() == null || method
								.getParameterTypes().length == 0)) {
					String property = method.getName();
					if (property.startsWith("get")) {
						property = property.substring(3);
						String lower = property.substring(0, 1).toLowerCase() + property.substring(1);
						map.put(lower, method.invoke(object, new Object[0]));
					} else if (property.startsWith("is")) {
						property = property.substring(2);
						String lower = property.substring(0, 1).toLowerCase() + property.substring(1);
						map.put(lower, method.invoke(object, new Object[0]));
					}
				}
			}
		}
		return map;
	}

	/**
	 * 获取类的所有属性名
	 *
	 * @param clazz 类元
	 * @return 属性名称集合
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public static List<String> getClassAllProperties(Class<?> clazz) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		if (clazz == null)
			return null;
		List<String> list = new ArrayList<String>();
		Method[] methods = clazz.getMethods();
		for (int i = 0, n = methods.length; i < n; i ++) {
			Method method = methods[i];
			if ((method.getModifiers() & Modifier.PUBLIC) == 1
					&& method.getDeclaringClass() != Object.class
					&& (method.getParameterTypes() == null || method
							.getParameterTypes().length == 0)) {
				String property = method.getName();
				if (property.startsWith("get") && ! "getClass".equals(property)) {
					property = property.substring(3);
					String lower = property.substring(0, 1).toLowerCase() + property.substring(1);
					list.add(lower);
				} else if (property.startsWith("is")) {
					property = property.substring(2);
					String lower = property.substring(0, 1).toLowerCase() + property.substring(1);
					list.add(lower);
				}
			}
		}
		return list;
	}

	/**
	 * 设置对象的属性值
	 *
	 * @param object 对象实例
	 * @param property 属性名
	 * @param value 待设置属性的值
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public static void setObjectProperty(Object object, String property, Object value) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		if (object == null)
			return;
		Method setter = getClassSetter(object.getClass(), property);
		setter.invoke(object, new Object[]{value});
	}

	/**
	 * 设置对象的多个属性值
	 *
	 * @param object 对象实例
	 * @param properties 属性集合
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public static void setObjectProperties(Object object, Map<String, Object> properties) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		if (object == null)
			return;
		for (Iterator<Map.Entry<String, Object>> iterator = properties.entrySet().iterator(); iterator.hasNext();) {
			Map.Entry<String, Object> entry = iterator.next();
			String property = entry.getKey();
			Object value = entry.getValue();
			Method setter = getClassSetter(object.getClass(), property);
			setter.invoke(object, new Object[]{value});
		}
	}

	/**
	 * 查找getXXX与isXXX的属性Getter方法
	 *
	 * @param clazz 类元
	 * @param property 属性名
	 * @return 属性Getter方法
	 * @throws NoSuchMethodException Getter不存时抛出
	 */
	public static Method getClassGetter(Class<?> clazz, String property) throws NoSuchMethodException, SecurityException {
		if (clazz == null)
			throw new java.lang.NullPointerException("类元不能为空！");
		if (property == null)
			throw new java.lang.NullPointerException("属性名不能为空！");
		property = property.trim();
		String upper = property.substring(0, 1).toUpperCase() + property.substring(1);
		try {
			return clazz.getMethod("get" + upper, new Class[0]);
		} catch (Exception e) {
			return clazz.getMethod("is" + upper, new Class[0]);
		}
	}

	/**
	 * 查找setXXX的属性Setter方法
	 *
	 * @param clazz 类元
	 * @param property 属性名
	 * @return 属性Setter方法
	 * @throws NoSuchMethodException Setter不存时抛出
	 */
	public static Method getClassSetter(Class<?> clazz, String property) throws NoSuchMethodException, SecurityException {
		if (clazz == null)
			throw new java.lang.NullPointerException("类元不能为空！");
		if (property == null)
			throw new java.lang.NullPointerException("属性名不能为空！");
		property = property.trim();
		String upper = property.substring(0, 1).toUpperCase() + property.substring(1);
		return clazz.getMethod("set" + upper, new Class[0]);
	}
	
	
	/** 

	 * 判断一个类是JAVA类型还是用户定义类型 

	 * @param clz 

	 * @return 

	 */  
	public static boolean isJavaClass(Class<?> clz) {
	    return clz != null && clz.getClassLoader() == null;     
	}
	
	public static List<Class<?>> getClasses(String packageName){
        //第一个class类的集合  

        List<Class<?>> classes = new ArrayList<Class<?>>();  
        //是否循环迭代  

        boolean recursive = true;  
        //获取包的名字 并进行替换  

        String packageDirName = packageName.replace('.', '/');  
        //定义一个枚举的集合 并进行循环来处理这个目录下的things  

        Enumeration<URL> dirs;  
        try {  
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);  
            //循环迭代下去  

            while (dirs.hasMoreElements()){  
                //获取下一个元素  

                URL url = dirs.nextElement();  
                //得到协议的名称  

                String protocol = url.getProtocol();  
                //如果是以文件的形式保存在服务器上  

                if ("file".equals(protocol)) {  
                    //获取包的物理路径  

                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");  
                    //以文件的方式扫描整个包下的文件 并添加到集合中  

                    findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);  
                } else if ("jar".equals(protocol)){  
                    //如果是jar包文件   

                    //定义一个JarFile  

                    JarFile jar;  
                    try {  
                        //获取jar  

                        jar = ((JarURLConnection) url.openConnection()).getJarFile();  
                        //从此jar包 得到一个枚举类  

                        Enumeration<JarEntry> entries = jar.entries();  
                        //同样的进行循环迭代  

                        while (entries.hasMoreElements()) {  
                            //获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件  

                            JarEntry entry = entries.nextElement();  
                            String name = entry.getName();  
                            //如果是以/开头的  

                            if (name.charAt(0) == '/') {  
                                //获取后面的字符串  

                                name = name.substring(1);  
                            }  
                            //如果前半部分和定义的包名相同  

                            if (name.startsWith(packageDirName)) {  
                                int idx = name.lastIndexOf('/');  
                                //如果以"/"结尾 是一个包  

                                if (idx != -1) {  
                                    //获取包名 把"/"替换成"."  

                                    packageName = name.substring(0, idx).replace('/', '.');  
                                }  
                                //如果可以迭代下去 并且是一个包  

                                if ((idx != -1) || recursive){  
                                    //如果是一个.class文件 而且不是目录  

                                    if (name.endsWith(".class") && !entry.isDirectory()) {  
                                        //去掉后面的".class" 获取真正的类名  

                                        String className = name.substring(packageName.length() + 1, name.length() - 6);  
                                        try {  
                                            //添加到classes  

                                            classes.add(Class.forName(packageName + '.' + className));  
                                        } catch (ClassNotFoundException e) {  
                                            e.printStackTrace();  
                                        }  
                                      }  
                                }  
                            }  
                        }  
                    } catch (IOException e) {  
                        e.printStackTrace();  
                    }   
                }  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
         
        return classes;  
    }  
	
	/** 

     * 以文件的形式来获取包下的所有Class 

     * @param packageName 

     * @param packagePath 

     * @param recursive 

     * @param classes 

     */  
    private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes){  
        //获取此包的目录 建立一个File  

        File dir = new File(packagePath);  
        //如果不存在或者 也不是目录就直接返回  

        if (!dir.exists() || !dir.isDirectory()) {  
            return;  
        }  
        //如果存在 就获取包下的所有文件 包括目录  

        File[] dirfiles = dir.listFiles(new FileFilter() {  
        //自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)  

              public boolean accept(File file) {  
                return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));  
              }  
            });  
        //循环所有文件  

        for (File file : dirfiles) {  
            //如果是目录 则继续扫描  

            if (file.isDirectory()) {  
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(),  
                                      file.getAbsolutePath(),  
                                      recursive,  
                                      classes);  
            }  
            else {  
                //如果是java类文件 去掉后面的.class 只留下类名  

                String className = file.getName().substring(0, file.getName().length() - 6);  
                try {  
                    //添加到集合中去  

                    classes.add(Class.forName(packageName + '.' + className));  
                } catch (ClassNotFoundException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }
    
    public static Object defaultValue(Class<?> type){
		if(type == int.class || type == byte.class || type == short.class){
			return 0;
		}else if(type == boolean.class){
			return false;
		}else if(type == long.class){
			return 0L;
		}else if(type == double.class){
			return 0.0d;
		}
		return null;
	}
    
    static PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
    
    public static Object parseValue(String value,Class<?> paramType){
    	if(StringUtils.isEmpty(value)){
    		return defaultValue(paramType);
    	}
    	Object returnVal = null;
    	try{
			if(paramType == String.class)
				returnVal = value;
			else if(paramType == int.class || paramType == Integer.class)
				returnVal = Integer.parseInt(value);
			else if(paramType == boolean.class || paramType == Boolean.class)
				returnVal = Boolean.parseBoolean(value);
			else if(paramType == double.class || paramType == Double.class)
				returnVal = Double.parseDouble(value);
			else if(paramType == Class.class)
				returnVal = Class.forName(value);
			else if(paramType == Resource[].class)
				returnVal = patternResolver.getResources(value);
			else if(paramType == Resource.class)
				returnVal = patternResolver.getResource(value);
		}catch(Exception e){
			returnVal = defaultValue(paramType);
		}
    	return returnVal;
    }
    
    static final String RESOURCE_PATTERN = "/**/*.class";  
    static ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); 
    
    public static Set<Class<?>> getClassSet(String basePackage){
    	Set<Class<?>> classSet = new HashSet<Class<?>>();
    	try{
    		String[] packages = basePackage.split(",");
    		for(String pkg : packages){
		        String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +  
		                org.springframework.util.ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;  
		        Resource[] resources = resourcePatternResolver.getResources(pattern);  
		        MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);  
		        for (Resource resource : resources) {  
		            if (resource.isReadable()) {  
		                MetadataReader reader = readerFactory.getMetadataReader(resource);  
		                String className = reader.getClassMetadata().getClassName();  
		                try{
		                	classSet.add(org.springframework.util.ClassUtils.forName(className,
		                			org.springframework.util.ClassUtils.getDefaultClassLoader()));
		                }catch(Throwable e){
		                	e.printStackTrace();
		                	LOG.error("Class Load Error className:" + className + "\t" + e.getMessage());
		                }
		            }  
		        }  
    		}
    	}catch(Exception e){
    		e.printStackTrace();
    	}
        return classSet; 
    }  
}
